package middleware.p2p;

import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.util.GregorianCalendar;
import java.util.LinkedList;
import java.util.Random;
import java.util.concurrent.LinkedBlockingQueue;

import javax.xml.bind.JAXBException;

import middleware.p2p.log.MiddlewareLogEvent;
import middleware.p2p.log.VectorClock;

public class ClockOrderTest {
	public final LinkedBlockingQueue<MiddlewareLogEvent> blkEventQueue;
	public final LinkedList<MiddlewareLogEvent> lnkOrdQueue;
	public final String testCids[]={"uno","due","tre"};
	
	public ClockOrderTest() {
		this.blkEventQueue=new LinkedBlockingQueue<MiddlewareLogEvent>();
		this.lnkOrdQueue=new LinkedList<MiddlewareLogEvent>();
		
	}
	
	public class SorterThread extends Thread {
		private LinkedBlockingQueue<MiddlewareLogEvent> incoming;
		private LinkedList<MiddlewareLogEvent> orderedQueue;
		public SorterThread(LinkedBlockingQueue<MiddlewareLogEvent> incoming,LinkedList<MiddlewareLogEvent> orderedQueue) {
			this.incoming=incoming;
			this.orderedQueue=orderedQueue;
		}
		/* 
		 * Metodo che si occupa di effettuare l'ordinamento e 
		 * l'eventuale delivery degli oggetti presenti in coda ogni minuto
		 */	
	    public void run() {
	    	while (true) {

/////////////////////////////////////////////////////////////////////////////////
	    		MiddlewareLogEvent temp;
	    		
	    		if(!incoming.isEmpty()){
	    			temp = incoming.poll();
	    			try {
						sortedPut(temp);
					} catch (FileNotFoundException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} catch (JAXBException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
	    			
	    		}
	    	}	
	    }
		/*	Metodo che inserisce in ordine crescente di VC i MiddlewareLogEvent nella coda ordinata
		 * 	@param temp il MiddlewareLogEvent da inserire
		 */
		private void sortedPut(MiddlewareLogEvent temp) throws FileNotFoundException, JAXBException {
			int i=0;
			
			if(orderedQueue.isEmpty()) orderedQueue.add(temp);
			else {
				i=getIndex(temp);
				//Se maggiore di tutti i VC in coda aggiunge alla fine
				if(i==-1) orderedQueue.addLast(temp);
				//In caso contrario aggiunge in posizione corretta
				else orderedQueue.add(i, temp);
				}
			deliver();
		}
	
		/* Metodo per cercare l'indice in cui inserire il MiddlewareLogEvent
		 * @param il MiddlewareLogEvent da inserire
		 * @return int l'indice cercato o -1 se maggiore di tutti gli elementi presenti in coda
		 */
		private int getIndex(MiddlewareLogEvent elem) {
	
			VectorClock VC = elem.getVclock();
			LinkedList<String> keys = VC.getAllKey();
			
			for(int i=0; i < orderedQueue.size(); i++){
				
				switch (compare(
						VC,
						orderedQueue.get(i).getVclock(),keys)){
					
				case 0: return getConcurrentIndex(elem);
				case 1: return i;
				case -1:
				}
			}
			return -1;
		}
		
		/* 	Trova l'indice in base al componentID o nel caso di uguaglianza in base al Timestamp
		 * 	@param MiddlewareLogEvent elem l'elemento da inserire
		 * 	@return int i l'indice in cui inserirlo o -1 se va inserito in coda
		 */
		private int getConcurrentIndex(MiddlewareLogEvent elem) {
			for(int i=0; i < orderedQueue.size(); i++){
				if(elem.getComponentID().compareTo(orderedQueue.get(i).getComponentID()) < 0) return i;
				if(elem.getComponentID().compareTo(orderedQueue.get(i).getComponentID()) < 0) return getTSindex(elem,i);
				
			}
			return -1;
		}
		
		/* 	Trova l'indice in base al Timestamp di due MiddlewareLogEvent inviati dallo stesso componente
		 * 	@param MiddlewareLogEvent elem l'elemento da inserire
		 * 	@param int l'indice dell'elemento in coda con componentId uguale a quello di elem
		 * 	@return int l'indice in cui inserirlo o -1 se va inserito in coda
		 */
		private int getTSindex(MiddlewareLogEvent elem,int i) {
				if(elem.getTimestamp() < orderedQueue.get(i).getTimestamp()) return i;
				else if(i==(orderedQueue.size()-1)) return -1;
				return i+1;
		}
		
		
		/*	Stabilisce se un VectorClock è minore, maggiore o concorrente rispetto ad un'altro
		 * 	@param first VC
		 * 	@param second VC
		 * 	@param keys chiavi del VC "first"
		 * 	@return int 0 se concorrenti, 1 se first < second, -1 se first > second
		 */
		private int compare(VectorClock first, VectorClock second, LinkedList<String> keys) {	
			String componentId;			
			
			for(int i=0; i < keys.size(); i++){
				componentId = keys.get(i);
				if(first.getClock(componentId) > second.getClock(componentId)) {
					if(i!=0) return 0;
					else{
						for(i=1;i<keys.size();i++){
							componentId =keys.get(i);
							if(first.getClock(componentId) <= second.getClock(componentId)) return 0;
						}
						return -1;
					}
				}
			}
			
			return 1;
		}
	
		/*	Metodo che consegna tutti i messaggi stabili
		 */
		private void deliver() throws JAXBException, FileNotFoundException {
			boolean flag = true;
			long TS;
			MiddlewareLogEvent elem;
			LinkedList<String> keys = new LinkedList<String>();
			
			while(flag){
				elem = orderedQueue.getFirst();
				TS = elem.getTimestamp();
				keys=elem.getVclock().getAllKey();
				
				for(int i=0; i < keys.size(); i++){
					MiddlewareLogEvent aux = find(keys.get(i));
					if(!(aux!=null && aux.getTimestamp() > TS)) {
						flag = false;
						break; //esce anche dal while? 
					}
				}
				if(flag) {
					//TODO consegna elem
				       MiddlewareLogEvent element = orderedQueue.getFirst();
				       try{
				    	   FileWriter os = new FileWriter( "globalLOG.txt.",true);
				    	   os.write("Worker '" + element.getComponentID() + "' sent:\n<< " + element.getDescription() + " >>\n"+element.getVclock().toString()+"\n");
				    	   os.flush();
				    	   os.close();
				       }catch(Exception e){
				    	   e.printStackTrace();
				       }
					orderedQueue.remove();	
				}	
			}
		}
		
		/*	Metodo che cerca un elemento nelle coda orderedQueue in base al componentID
		 *  @param String componentId il componentId cercato
		 *  @retutn MiddlewareLogEvent l'elemento se presente o null altrimenti
		 */
		private MiddlewareLogEvent find(String componentId){
			
			for(int i=1; i< orderedQueue.size(); i++){
				if(orderedQueue.get(i).getComponentID()==componentId) return orderedQueue.get(i); 
			}
			return null;
		}
	}

	
	
//////////////////////////////////////////////////////////////////////////////////////////////////////////
	public class PusherThread extends Thread {
		private LinkedBlockingQueue<MiddlewareLogEvent> queue;
		private String cids[];
		public PusherThread(LinkedBlockingQueue<MiddlewareLogEvent> q,String cids[]) {
			this.queue=q;
			this.cids=cids;
		}
		public void run() {
			while(true) {
				MiddlewareLogEvent event=new MiddlewareLogEvent();
				Random rand=new Random(new GregorianCalendar().getTimeInMillis());
				event.setComponentID(this.cids[rand.nextInt(this.cids.length)]);
				event.setTimestamp(rand.nextLong());
				event.setDescription("Evento "+rand.nextInt());
				VectorClock vc=new VectorClock();
				for (int k=0;k<this.cids.length;k++) {
					vc.setClock(this.cids[k], rand.nextInt(100));
				}
				event.setVclock(vc);
				try {
					this.queue.put(event);
					int ret= (int) (Math.random()%999);
					Thread.sleep(ret);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}				
			}
		}
	}
	public static void main(String args[]) {
		ClockOrderTest test=new ClockOrderTest();
		for (int k=0;k<test.testCids.length;k++) {
			PusherThread pt=test.new PusherThread(test.blkEventQueue, test.testCids);
			pt.start();
		}
		SorterThread st=test.new SorterThread(test.blkEventQueue, test.lnkOrdQueue);
		st.start();		
		try {
			st.join();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}